/*
 * Created on Mar 20, 2007
 *
 * Copyright (c) 2006-2007 P.J.Leonard
 * 
 * http://www.frinika.com
 * 
 * This file is part of Frinika.
 * 
 * Frinika is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * Frinika is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Frinika; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package uk.ac.bath.audio.gui;

import uk.ac.bath.audio.*;
import uk.ac.bath.audio.CyclicSpectrumDataBuilder;
import uk.ac.bath.audio.Config;

import java.awt.Dimension;
import java.util.Vector;

import uk.org.toot.audio.core.AudioBuffer;
import uk.org.toot.audio.core.AudioProcess;


/**
 * Creates a spectrogram from a DoubleDataSource
 * 
 * Observers are notified when data changes (during build)
 * 
 * SizeObserver are notify when the number of frequency bins is changed.
 * 
 * @author pjl
 * 
 */
public class CyclicBufferFFTSpectrogramDataBuilder 	implements CyclicSpectrumDataBuilder {

	Vector<CyclicSpectrogramDataListener> sizeObservers = new Vector<CyclicSpectrogramDataListener>();

	private AudioProcess reader;

    FFTWorker fftWorker;


    private float [][] data;
	private float[][] magnArray;

    double fftBuffer[][];

	//private float[][] smoothMagnArray;

	//private float[][] phaseArray;

	//private float[][] dPhaseFreqHz;



	int chunkPtr = 0;

	private int sizeInChunks;


	double Fs = Config.sampleRate;

	//int nFrame;

	int chunksize;

	int fftsize;

	double dt;

	Dimension size;

	private int chunkStartInSamples;

	private int totalFramesRendered;

	
	//private double[] dPhaRef;


	private double[] input;

//	private double logMagn[];
	
//	private double twoPI;

	private AudioBuffer buffer;

	boolean abortFlag=false;

	private boolean running=false;

	private Thread runThread=null;

	private Thread abortWaiter;
    private FFTMagnitude fftMagn;
	
	/**
	 * 
	 * @param minF
	 * @param nOctave
	 * @param binsPerOctave
	 */
	public CyclicBufferFFTSpectrogramDataBuilder(AudioProcess reader,
			int bufferSize,double Fs) {
		this.reader = reader;
		this.sizeInChunks = bufferSize;
//		twoPI = 2 * Math.PI;
        fftWorker=new FFTWorker(Fs,true);
        fftMagn=new FFTMagnitude();
        data=new float[1][];
    }

	synchronized public void setParameters(int chunksize, int fftsize) {

		
		System.err.println(" AAAA ");
		
		
		if (chunksize == this.chunksize && fftsize == this.fftsize)	return;		
		
		System.out.println(" ABORT REQUEST "+System.currentTimeMillis());
		
		abortFlag=true;
		abortWaiter=Thread.currentThread();
		
		while(running) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		abortWaiter=null;
		
		System.out.println(" ABORT DONE "+System.currentTimeMillis());

		
		buffer = new AudioBuffer("TEMP", 1, chunksize, 44100);
		Fs = Config.sampleRate;

		this.chunksize = chunksize;
		this.fftsize = fftsize;

		dt = chunksize / Fs;

		System.out.println(" RESIZE FFT REQUEST "+System.currentTimeMillis());
		resize();
		System.out.println(" RESIZE FFT DONE "+System.currentTimeMillis());
		
		Runnable runner= new Runnable() {
			public void run() {
				doWork();
				runThread=null;
			}
		};
		
		runThread = new Thread(runner);
		runThread.start();
	}

	
	public void abortConstruction() {

	
	}
	
	
	public void addSizeObserver(CyclicSpectrogramDataListener o) {
		sizeObservers.add(o);
	}

	void notifyMoreDataObservers(float buff[]) {
		for (CyclicSpectrogramDataListener o : sizeObservers)
			o.notifyMoreDataReady(buff);

	}

	public int getSizeInChunks() {
		return sizeInChunks;
	}

	public int getChunkRenderedCount() {
		return totalFramesRendered;
	}

//	public int getBinCount() {
//		return nBin;
//	}

//	public float[][] getMagnitude() {
//		return magnArray;
//	}
//
//
//	public float[][] getSmoothMagnitude() {
//		return smoothMagnArray;
//	}

	synchronized void resize() {
        fftWorker.resize(fftsize);
        
	

		/*
		 * Here the size of arrays changes any user should synchronize with me
		 * here
		 */

        int nBin=fftWorker.getSizeInBins();
        
		for (int i = 0; i < nBin; i++) {
//			freq[i] = (i * Fs / nBin);
//			freqArray[i] = (float) freq[i];

			// System.out.println(" fftsize/chunkSIze = " + fftsize + "/"
			// + chunksize);

			size = new Dimension(sizeInChunks, nBin);

//			dPhaseFreqHz = new float[sizeInChunks][nBin];

			magnArray = new float[sizeInChunks][nBin];
            fftBuffer = new double[sizeInChunks][fftsize];

//			smoothMagnArray = new float[sizeInChunks][nBin];
//			logMagn = new double[nBin*2];
//			phaseArray = new float[sizeInChunks][nBin];
		}

	
		// Phase change (radians) of each bin due to chunksize translation
//		dPhaRef = new double[nBin];
//		for (int i = 0; i < nBin; i++) {
//			dPhaRef[i] = (twoPI * freq[i] * dt); // >0
//		}

	
		input = new double[fftsize];
		
		System.out.println(" Resized " + fftsize);


	}

	protected void doWork() {

		running =true;
		abortFlag=false;
		chunkPtr = 0; // make invalid
	
	//	int nRead = 0;

		chunkStartInSamples = 0;
	//	float phaLast[] = phaseArray[0];

		
		totalFramesRendered = 0;
	//	notifySizeObservers();
		
		do {
			
			if (abortFlag) break;
			if (fftsize != chunksize) {
				for (int i = 0; i < fftsize - chunksize; i++)
					input[i] = input[i + chunksize];
			}

			buffer.makeSilence();
            
            reader.processAudio(buffer);

			float left[] = buffer.getChannel(0);

			// System.out.println(" mm =" + left[0]);

			for (int i = fftsize - chunksize, j = 0; i < fftsize; i++, j++) {
				// if (ch == 2)
				// input[i] = buffer[2 * j + 1];
				// else
				input[i] = left[j];
			}

			if (chunkPtr < 0) {
				chunkPtr++;
				chunkStartInSamples += chunksize;
				continue;
			}


            fftWorker.process(input,fftBuffer[chunkPtr]);

            data[0]=magnArray[chunkPtr];
            fftMagn.getData(data, fftBuffer[chunkPtr]);
       
			// System.out.println(" maqxV " + maxV);

			notifyMoreDataObservers(magnArray[chunkPtr]);

			chunkPtr++;
			totalFramesRendered++;
			if (chunkPtr >= sizeInChunks)
				chunkPtr = 0;
			// System.out.println(vertPtr[0]);
			// bar.setValue(pix);
			// if (chunkPtr % 50 == 0) {

			// }
			// nFrame = (int) reader.getLengthInFrames();
			// } while (!reader.eof() && chunkPtr < sizeInChunks);
		} while (true);

		running=false;
		abortFlag=false;
		if (abortWaiter != null) abortWaiter.interrupt();
		System.out.println(" ABORTED ");

	}

	public float[] getFreqArray() {
		return fftWorker.getFreqArray();
	}

	public float[] getMagnitudeAt(long chunkPtr) {
		if (magnArray == null)
			return null;

		// int pix = (int) (framePtr / chunksize);
		if (chunkPtr >= magnArray.length || chunkPtr < 0)
			return null;
		return magnArray[(int) chunkPtr];
	}
//
//	public float[] getPhaseAt(long chunkPtr) {
//		if (phaseArray == null)
//			return null;
//
//		// int pix = (int) (framePtr / chunksize);
//		if (chunkPtr >= phaseArray.length || chunkPtr < 0)
//			return null;
//		return phaseArray[(int) chunkPtr];
//	}
//
//	public float[] getPhaseFreqAt(long chunkPtr) {
//		if (dPhaseFreqHz == null)
//			return null;
//
//		// int pix = (int) (framePtr / chunksize);
//		if (chunkPtr >= dPhaseFreqHz.length)
//			return null;
//		return dPhaseFreqHz[(int) chunkPtr];
//	}

	// public long getLengthInFrames() {
	// return reader.getLengthInFrames();
	// }

	public long chunkStartInSamples(long chunkPtr) {

		return chunkStartInSamples + chunkPtr * chunksize;
	}

	public int getChunkAtFrame(long framePtr) {

		int chunkPtr1 = (int) ((framePtr - chunkStartInSamples) / chunksize);

		return chunkPtr1;
	}

	public boolean validAt(long chunkPtr2) {
		return chunkPtr2 >= 0 && chunkPtr2 < this.chunkPtr;
	}

	public double getSampleRate() {
		// TODO Auto-generated method stub
		return Fs;
	}

    public int getBinCount() {
        return fftWorker.getSizeInBins(); //throw new UnsupportedOperationException("Not supported yet.");
    }

    public float[][] getMagnitude() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}
